package jgrit.options;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

import static jgrit.Constants.*;

/**
 * 
 * Collection of options for interacting with the services
 * @author Taylor Perkins
 *
 */
public class OptionCollection {
	
	// Graphics Options
	public MultiOption format;
	public MultiOption graphicsCompression;
	public MultiOption bitDepth;
	/**
	 * Hex value representing the transparent color.
	 */
	public HexOption transColor;

	// Area Options
	public BooleanOption areaIsCustom;
	public RangedIntOption areaLeft; 
	public RangedIntOption areaTop;
	public RangedIntOption areaWidth;
	public RangedIntOption areaHeight;
	
	// Palette Options
	public RangedIntOption paletteStart;
	public RangedIntOption paletteCount; 
	public RangedIntOption paletteTrans;
	public BooleanOption paletteEnabled;
	
	// Meta-tile Options
	public RangedIntOption metaTileHeight;
	public RangedIntOption metaTileWidth;
	public BooleanOption metaTilePalReduction;
	
	// Map Options
	public RangedIntOption tileOffset;
	public MultiOption mapCompression;
	public MultiOption mapLayout;
	public BooleanOption mapTileReduction;
	public BooleanOption mapPalReduction;
	public BooleanOption mapFlippedReduction;
	
	// File Options
	/**
	 * List of source images (<code>File</code>) to perform the task on
	 */
	public List<File> sourceFiles;
	
	/**
	 * Format of the output file.
	 */
	public MultiOption fileFormat;
	public MultiOption arrayDataType;
	public BooleanOption includeHeader;
	public BooleanOption pedantic;
	public BooleanOption append;
	/**
	 * Enable GRF-format for non-grf filetype
	 */
	public BooleanOption riffOption;
	/**
	 * Represents the base name for the symbol in the output file
	 */
	public Option symbolBaseName;
	/**
	 * Represents an external tileset to use
	 */
	public Option externalTileset;
	/**
	 * Output destination folder
	 */
	public Option destinationFile;
	
	/**
	 * Initializes all options to their default values. Before use, files and
	 * images should be set using the appropriate methods.
	 */
	public OptionCollection() {
		this(new ArrayList<File>(), new ArrayList<BufferedImage>());
	}
	
	/**
	 * Initializes all options to their default values.
	 * 
	 * @param files  the input image files.
	 * @param images the decoded input images; each element corresponds to
	 *               the same-numbered element in the 'files' parameter.
	 */
	public OptionCollection(List<File> files, List<BufferedImage> images) {		
		initGraphicOptions();
		initPaletteOptions();
		initMapOptions();
		initFileOptions();
		setFiles(files);
		
		setImages(images);
	}
	
	/**
	 * Set the current image. Currently a wrapper around setImages.
	 * 
	 * @param image The image as a BufferedImage
	 */
	public void setImage(BufferedImage image) {
		setImages(Arrays.asList(image));
	}
	
	/**
	 * Set the current image(s). Right now, it assumes there is only one (first
	 * one is used).
	 * 
	 * @param images List of images
	 */
	public void setImages(List<BufferedImage> images) {		
		if (images.size() > 0) {
			areaWidth.setValue(String.valueOf(images.get(0).getWidth()));
			areaHeight.setValue(String.valueOf(images.get(0).getHeight()));
		}
	}

	/**
	 * Set a single file to be used (wrapper around setFiles, at least for now).
	 * 
	 * @param file The image file
	 */
	public void setFile(File file) {
		setFiles(Arrays.asList(file));
	}
	
	/**
	 * Set the files being used.
	 * 
	 * @param files List<File> list of the files to use
	 */
	public void setFiles(List<File> files) {
		sourceFiles = files;
		
		if (files.size() > 0) {
			// Set the symbol base name
			String baseName = files.get(0).getName();
			baseName = baseName.substring(0, baseName.lastIndexOf('.'));
			symbolBaseName.setValue(baseName);
			
			// Set the destination to a permutation of the input
			String absPath = files.get(0).getAbsolutePath();
			destinationFile.setValue(absPath.substring(0, absPath.lastIndexOf('.')) + fileType());
			/*
			 * FIXME: change ".c" to be dependent on the output type;
			 * probably also strip the extension off the filename first.
			 * Added as-is to make testing easier. 
			 */
//			String sourceFilePath = sourceFiles.get(0).getAbsolutePath();
//			sourceFilePath = sourceFilePath.substring(0, 
//					sourceFilePath.lastIndexOf('.'));
//			destinationFolder.setValue(sourceFilePath + ".c");
		}
	}
	
	/**
	 * Initialize the file options
	 */
	private void initFileOptions() {
		fileFormat = new MultiOption("Format", "File", FILEFORMAT_C, new String[] {
				FILEFORMAT_C,
				FILEFORMAT_GNUASM,
				FILEFORMAT_BINARY,
				FILEFORMAT_GBFS,
				FILEFORMAT_RIFF });
		//fileFormat is tied to the output file
		fileFormat.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				if (destinationFile != null) {
					String outputType = fileType();
					String currentType = destinationFile.getValue();
					currentType = currentType.substring(
							currentType.lastIndexOf('.'),
							currentType.length());
					//Don't update in this scenario
					if (currentType.equals(outputType))
						return;
					
					//outputType contains the ".<extension>" string
					
					String destination = destinationFile.getValue();
					if (destination.length() > 0) {
						//We have an output file						
						int extStart = destination.lastIndexOf('.');
						
						if (extStart >= 0) {
							destination = destination.substring(0, extStart) + outputType;
						} else {
							destination = destination + outputType;
						}
						destinationFile.setValue(destination);
					}
				}
			}
		});
		includeHeader = new BooleanOption("Include Header", "File", true);
		pedantic = new BooleanOption("Strict C89 Output", "File", true);
		append = new BooleanOption("Append (or Replace Existing Section)", "File", false);
		riffOption = new BooleanOption("Enable GRF-format for non-grf", "File", false);
		arrayDataType = new MultiOption("Array Data Type", "File", DATATYPE_U16, new String[] {
				DATATYPE_U8,
				DATATYPE_U16,
				DATATYPE_U32 });

		symbolBaseName = new Option("Symbol Base Name", "File", "");
		externalTileset = new Option("External Tileset", "File", "");
		
		destinationFile = new Option("Destination", "File", "");
		//Destination folder is tied to the fileFormat combo box
		destinationFile.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				String currentDest = destinationFile.getValue();
				int fileExtensionIndex = Math.max(0, currentDest.lastIndexOf('.'));
				String currentType = currentDest.substring(fileExtensionIndex);
				
				if (currentType.equals(".c")) {
					fileFormat.setValue(FILEFORMAT_C);
				} else if (currentType.equals(".s")) {
					fileFormat.setValue(FILEFORMAT_GNUASM);
				} else if (currentType.equals(".bin")) {
					fileFormat.setValue(FILEFORMAT_BINARY);
				} else if (currentType.equals(".gbfs")) {
					fileFormat.setValue(FILEFORMAT_GBFS);
				} else if (currentType.equals(".grf")) {
					fileFormat.setValue(FILEFORMAT_RIFF);
				} else {
					fileFormat.setValue(FILEFORMAT_C);
					currentType = ".c";
					if (fileExtensionIndex > 0)
						currentDest = 
							currentDest.substring(0, currentDest.lastIndexOf('.'));
					destinationFile.setValue(currentDest + currentType);
				}
			}
		});
	}

	/**
	 * Initialize map options
	 */
	private void initMapOptions() {
		mapCompression = new MultiOption("Compression", "Map", COMPRESSION_OFF, new String[] {
				COMPRESSION_OFF,
				COMPRESSION_LZ77,
				COMPRESSION_HUFF,
				COMPRESSION_RLE,
				COMPRESSION_OFFANDHDR });
		mapLayout = new MultiOption("Layout", "Map", "OFF", 
				new String[] { "OFF", MAPLAYOUT_FLAT, MAPLAYOUT_SBB, MAPLAYOUT_AFFINE });
		mapTileReduction = new BooleanOption("Tile Reduction", "Map", true);
		mapPalReduction = new BooleanOption("Palette", "Map", true);
		mapFlippedReduction = new BooleanOption("Flip", "Map", true);
		metaTilePalReduction = new BooleanOption("Meta Tile Palette Reduction", "Map", false);
		tileOffset = new RangedIntOption("Tile Offset", "Map", "0", 0, Integer.MAX_VALUE);
	}

	/**
	 * Initialize palette options
	 */
	private void initPaletteOptions() {
		paletteEnabled = new BooleanOption("Include Palette", "Palette", true);
		paletteStart = new RangedIntOption("Start", "Palette", "0", 0, 256);
		paletteCount = new RangedIntOption("Count", "Palette", "256", 0, 256);
		paletteTrans = new RangedIntOption("Trans", "Palette", "0", 0, 256);
		metaTileHeight = new RangedIntOption("Height", "Palette", "1", 0, Integer.MAX_VALUE);
		metaTileWidth = new RangedIntOption("Width", "Palette", "1", 0, Integer.MAX_VALUE);
	}

	/**
	 * Initialize the graphics options
	 */
	private void initGraphicOptions() {
		format = new MultiOption("Format", "Graphics", GFXFORMAT_TILE, new String[] {
				GFXFORMAT_TILE,
				GFXFORMAT_BITMAP_GBA,
				GFXFORMAT_BITMAP_NDS,
				GFXFORMAT_BITMAP_ALPHA_NDS });
		graphicsCompression = new MultiOption("Compression", "Graphics", COMPRESSION_OFF, new String[] {
				COMPRESSION_OFF,
				COMPRESSION_LZ77,
				COMPRESSION_HUFF,
				COMPRESSION_RLE,
				COMPRESSION_OFFANDHDR });
		bitDepth = new MultiOption("Bits Per Pixel", "Graphics", "8", new String[] { "1", "2", "4", "8", "16" });
		transColor = new HexOption("Transparent Color (hex)", "Graphics", "FF00FF", 6);
		areaLeft = new RangedIntOption("Left", "Graphics", "0", 0, Integer.MAX_VALUE);
		areaTop = new RangedIntOption("Top", "Graphics", "0", 0, Integer.MAX_VALUE);
		areaIsCustom = new BooleanOption("Custom", "Area", false);
		
		// Set the size of the images later (in setImages)
		areaWidth = new RangedIntOption("Width", "Graphics", "0", 0, Integer.MAX_VALUE);
		areaHeight = new RangedIntOption("Height", "Graphics", "0", 0, Integer.MAX_VALUE);
	}
	
	/**
	 * Returns the file extension in string form
	 * @return - String form of the file extension
	 */
	private String fileType() {
		String outputType = fileFormat.getValue();
		outputType = outputType.substring(
				outputType.lastIndexOf('.'), 
				outputType.length() - 1);
		
		return outputType;
	}
	
	/**
	 * Returns a list of restricted options (for disabling the ok button on
	 * invalid data)
	 * @return - Collection of restricted options
	 */
	public Collection<Option> getRestrictedOptions() {
		Collection<Option> restricted = new ArrayList<Option>();
		restricted.add(transColor);
		restricted.add(areaLeft); 
		restricted.add(areaTop);
		restricted.add(areaWidth);
		restricted.add(areaHeight);
		restricted.add(paletteStart);
		restricted.add(paletteCount); 
		restricted.add(paletteTrans);
		restricted.add(metaTileHeight);
		restricted.add(metaTileWidth);
		restricted.add(tileOffset);
		restricted.add(symbolBaseName);
		restricted.add(externalTileset);
		restricted.add(destinationFile);
		return restricted;
	}
	
	/**
	 * Returns the matching header file name for the destination file, whether it exists or not.
	 * @return String name of the header file
	 */
	public String getHeaderFileName()
	{
		String destFile = destinationFile.getValue();
		String fileName = "";
		
	    fileName = destFile.substring(0, destFile.length() - 2);
		
		fileName += ".h";
		
		return fileName;
	}
}
